#include <Windows.h>
#include <stdint.h>
#include <stdio.h>
#include <comdef.h>

#include "IHelloWorld.hpp"
#include "CMyComClass.hpp"
#include "CMyComClassFactory.hpp"
#include "DllServer.h"

HINSTANCE ghInstance = NULL;        // Our DLL's module handle
uint32_t gdwDllLockCount = 0;      // # of COM objects in existence

/*
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\WOW6432Node\CLSID\{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}
Default = Name (REG_SZ)

HKEY_LOCAL_MACHINE\SOFTWARE\Classes\WOW6432Node\CLSID\{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}\InProcServer32
Default = DLL Path (REG_SZ)
ThreadingModel = "Apartment" (REG_SZ)
*/

BOOL APIENTRY DllMain(HINSTANCE hModule,
    DWORD     ul_reason_for_call,
    LPVOID    lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        ghInstance = hModule;

        // Calling DisableThreadLibraryCalls() prevents DllMain() from 
        // getting called for every thread that attaches/detaches from
        // our DLL.

        DisableThreadLibraryCalls(hModule);
    }
    default: break;
    }

    return TRUE;
}


STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv)
{
    HRESULT hrRet = S_OK;
    CMyComClassFactory* pFactory;
    MessageBoxW(NULL, L"DllGetClassObject called", nullptr, 0);
    // Check that the client is asking for the CSimpleMsgBoxImpl factory.

    if (!InlineIsEqualGUID(rclsid, __uuidof(CMyComClass)))
        return CLASS_E_CLASSNOTAVAILABLE;

    // Check that ppv really points to a void*.

    if (IsBadWritePtr(ppv, sizeof(void*)))
        return E_POINTER;

    *ppv = NULL;

    // Construct a new class factory object.

    pFactory = new CMyComClassFactory;

    if (NULL == pFactory)
        return E_OUTOFMEMORY;

    // AddRef() the factory since we're using it.

    pFactory->AddRef();

    // QI() the factory for the interface the client wants.

    hrRet = pFactory->QueryInterface(riid, ppv);

    // We're done with the factory, so Release() it.

    pFactory->Release();
    
    return hrRet;
}

// DllCanUnloadNow() is called when COM wants to unload our DLL from memory.
// We check our lock count, which will be nonzero if there are any COM
// objects still in memory.
// Return S_FALSE to prevent the DLL from being unloaded, or S_OK to let it
// be unloaded.
STDAPI DllCanUnloadNow() {
    return gdwDllLockCount > 0 ? S_FALSE : S_OK;
}

STDAPI DllRegisterServer() {
    HKEY  hCLSIDKey = NULL, hInProcSvrKey = NULL;
    LONG  lRet = 0;
    wchar_t szModulePath[MAX_PATH] = { 0 };
    wchar_t szClassDescription[] = L"SimpleMsgBox class";
    wchar_t szThreadingModel[] = L"Apartment";

    // Create a key under CLSID for our COM server.

    lRet = RegCreateKeyExW(HKEY_CLASSES_ROOT, L"CLSID\\{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}",
        0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE | KEY_CREATE_SUB_KEY,
        NULL, &hCLSIDKey, NULL);

    if (lRet == ERROR_SUCCESS) {
        // The default value of the key is a human-readable description of the coclass.

        lRet = RegSetValueExW(hCLSIDKey, NULL, 0, REG_SZ, (const BYTE*)szClassDescription, sizeof(szClassDescription));

        if (lRet == ERROR_SUCCESS) {
            // Create the InProcServer32 key, which holds info about our coclass.

            lRet = RegCreateKeyExW(hCLSIDKey, L"InProcServer32", 0, NULL, REG_OPTION_NON_VOLATILE,
                KEY_SET_VALUE, NULL, &hInProcSvrKey, NULL);

            if (lRet == ERROR_SUCCESS) {
                // The default value of the InProcServer32 key holds the full path to our DLL.

                GetModuleFileNameW(ghInstance, szModulePath, MAX_PATH);

                lRet = RegSetValueExW(hInProcSvrKey, NULL, 0, REG_SZ, (const BYTE*)szModulePath,
                    sizeof(wchar_t) * (wcslen(szModulePath) + 1));

                if (lRet == ERROR_SUCCESS) {
                    // The ThreadingModel value tells COM how it should handle threads in our DLL.
                    // The concept of apartments is beyond the scope of this article, but for
                    // simple, single-threaded DLLs, use Apartment.

                    lRet = RegSetValueExW(hInProcSvrKey, L"ThreadingModel", 0, REG_SZ,
                        (const BYTE*)szThreadingModel,
                        sizeof(szThreadingModel));
                }

                RegCloseKey(hInProcSvrKey);
            }
        }

        RegCloseKey(hCLSIDKey);
    }

    return HRESULT_FROM_WIN32(lRet);
}

// DllUnregisterServer() deleted the registy entries that DllRegisterServer() created.
STDAPI DllUnregisterServer()
{
    // Delete our registry entries.  Note that you must delete from the deepest
    // key and work upwards, because on NT/2K, RegDeleteKey() doesn't delete 
    // keys that have subkeys on NT/2K.

    RegDeleteKeyW(HKEY_CLASSES_ROOT, L"CLSID\\{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}\\InProcServer32");
    RegDeleteKeyW(HKEY_CLASSES_ROOT, L"CLSID\\{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}");

    return S_OK;
}